package thread;

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
//import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import log.MyLogger;
import command.BaseReceiver;
import command.Command;
import command.impl.ConcreteCommand;
import command.receiver.ReturnToUserReceiver;
import command.receiver.SendToGroupUsersReceiver;
import command.receiver.SendToOneUserReceiver;
import pojo.OnlineSocket;
import pojo.Protocol;
import service.UserService;
import service.impl.UserServiceImpl;
//import util.Checkout;
import util.ProtocolJSONConverter;

public class SocketThread implements Runnable{
	
	private Map<String, OnlineSocket> onlineUser;
	private Socket socket;
	private String userName;
	private UserService userService;
	
	public SocketThread(Socket socket, Map<String, OnlineSocket> onlineUser) {
		this.socket = socket;
		this.onlineUser = onlineUser;
		this.userService = new UserServiceImpl();
	}

	public void run() {
		InputStream in = null;
		OutputStream out = null;
		
		try {
			in = socket.getInputStream();
			out = socket.getOutputStream();
			
			Command command = null;
			
			// begin loop to receive protocol sent from client
			boolean loop = true;
			BufferedReader reader = new BufferedReader(new InputStreamReader(in));
			
			while (loop) {
				Protocol protocol = null;
				
				try {
//					ObjectInputStream objIn = new ObjectInputStream(in);
//					protocol = (Protocol) objIn.readObject();
					
					protocol = ProtocolJSONConverter.toProtocol(reader.readLine());
				} catch (EOFException e) {
					MyLogger.log(e.getMessage());
				} catch (SocketException e) {
					// client crashed
					if (userName != null) {
						Protocol logoutUserPro = new Protocol(Protocol.SOMEONE_LOGOUT, null, userName, null, null);
						Command notifyLogoutCommand = new ConcreteCommand(new SendToGroupUsersReceiver(onlineUser, logoutUserPro));
						notifyLogoutCommand.execute();
						onlineUser.remove(userName);
					}
					
					MyLogger.log(e.getMessage());
					break;
				}
				
				if (protocol == null) {
					continue;
				}
				
				switch (protocol.getType()) {
				case Protocol.REGISTER:
					userName = protocol.getFrom();
					String regPassword = protocol.getMsg();
					
					if (userService.register(userName, regPassword)) {
						MyLogger.log("New registered user: " + userName);
						List<String> onlineUserList = new ArrayList<>(onlineUser.keySet());
						onlineUserList.remove(userName);
						Protocol errorPro = new Protocol(Protocol.REGISTER_SUCCESS, "Register success!", null, userName, onlineUserList);
						command = new ConcreteCommand(new ReturnToUserReceiver(out, errorPro));
						command.execute();
						
						// add user into map
						onlineUser.put(userName, new OnlineSocket(socket, out));
						
						// send new user to other users
						Protocol newUserPro = new Protocol(Protocol.SOMEONE_LOGIN, null, userName, null, null);
						command = new ConcreteCommand(new SendToGroupUsersReceiver(onlineUser, newUserPro));
						executeSendGroupCommand(command);
						MyLogger.log("notify all online users that a new user login");
					} else {
						MyLogger.log("Register failed!");
						Protocol errorPro = new Protocol(Protocol.ERROR, "Register failed!", null, userName, null);
						command = new ConcreteCommand(new ReturnToUserReceiver(out, errorPro));
						command.execute();
					}
					break;
				case Protocol.LOGIN:
					userName = protocol.getFrom();
					
					if (onlineUser.containsKey(userName)) {
						Protocol errorPro = new Protocol(Protocol.ERROR, "User already logged in!", null, userName, null);
						command = new ConcreteCommand(new ReturnToUserReceiver(out, errorPro));
						command.execute();
						break;
					}
					
					String loginPassword = protocol.getMsg();
					
					if (!userService.login(userName, loginPassword)){
						MyLogger.log("Login failed!");
						
						Protocol errorPro = new Protocol(Protocol.ERROR, "Login failed!", null, userName, null);
						command = new ConcreteCommand(new ReturnToUserReceiver(out, errorPro));
						command.execute();
						break;
					}
					
					// add user into map
					onlineUser.put(userName, new OnlineSocket(socket, out));
					
					// send success message and online user list to user
					MyLogger.log(userName + " login success.");
					List<String> onlineUserList = new ArrayList<>(onlineUser.keySet());
					onlineUserList.remove(userName);
					Protocol successMsgPro = new Protocol(Protocol.LOGIN_SUCCEED, "Login success!", null, userName, onlineUserList);
					command = new ConcreteCommand(new SendToOneUserReceiver(onlineUser, successMsgPro));
					command.execute();
					MyLogger.log("send login success message to "+userName);
					
					// send new user to other users
					Protocol newUserPro = new Protocol(Protocol.SOMEONE_LOGIN, null, userName, null, null);
					command = new ConcreteCommand(new SendToGroupUsersReceiver(onlineUser, newUserPro));
					executeSendGroupCommand(command);
					MyLogger.log("notify all online users that a new user login");

					break;
				case Protocol.PRIVATE_MSG:
				case Protocol.PRIVATE_SHAKE:
					System.out.println("Sending private message...");
					MyLogger.log("Send private msg/shake to one user");
					command = new ConcreteCommand(new SendToOneUserReceiver(onlineUser, protocol));
					command.execute();
					break;
				case Protocol.GROUP_MSG:
				case Protocol.GROUP_SHAKE:
					System.out.println("Sending group message...");
					MyLogger.log("Send group msg/shake to All online users");
					command = new ConcreteCommand(new SendToGroupUsersReceiver(onlineUser, protocol));
					executeSendGroupCommand(command);
					break;
				case Protocol.LOGOUT:
					System.out.println("Logging out...");
					MyLogger.log(userName+" log out");
					Protocol logoutSuccessPro = new Protocol(Protocol.LOGOUT, "Logout success!", null, userName, null);
					command = new ConcreteCommand(new SendToOneUserReceiver(onlineUser, logoutSuccessPro));
					command.execute();
					
					onlineUser.remove(userName);
					
					Protocol logoutUserPro = new Protocol(Protocol.SOMEONE_LOGOUT, null, userName, null, null);
					command = new ConcreteCommand(new SendToGroupUsersReceiver(onlineUser, logoutUserPro));
					executeSendGroupCommand(command);
					
					loop = false;
					break;
				}
				
				
			}

		} catch (IOException e) {
			e.printStackTrace();
			MyLogger.log(e.getMessage());
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
		try {
//			Thread.sleep(5000);
			if (socket != null) {
				socket.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
			MyLogger.log(e.getMessage());
		}
	}

	private void executeSendGroupCommand(Command command) throws IOException {
		Entry<Integer, List<Object>> result = command.execute();
		
		if (result != null && result.getKey() == BaseReceiver.USER_OFFLINE) {
			for (Object user : result.getValue()) {
				Protocol logoutUserPro = new Protocol(Protocol.SOMEONE_LOGOUT, null, user.toString(), null, null);
				Command notifyLogoutCommand = new ConcreteCommand(new SendToGroupUsersReceiver(onlineUser, logoutUserPro));
				notifyLogoutCommand.execute();
			}
		}
	}
}
